{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Quickstart\n",
    "\n",
    "Tutorial for [NautilusTrader](https://nautilustrader.io/docs/) a high-performance algorithmic trading platform and event driven backtester.\n",
    "\n",
    "[View source on GitHub](https://github.com/nautechsystems/nautilus_trader/blob/develop/docs/getting_started/quickstart.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## Overview\n",
    "\n",
    "This quickstart tutorial steps through how to get up and running with NautilusTrader backtesting using FX data.\n",
    "To support this, some pre-loaded test data is available using the standard Nautilus persistence format (Parquet)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "## Prerequisites\n",
    "- Python 3.10+ installed\n",
    "- [NautilusTrader](https://pypi.org/project/nautilus_trader/) latest release installed (`pip install -U nautilus_trader`)\n",
    "- [JupyterLab](https://jupyter.org/) or similar installed (`pip install -U jupyterlab`)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "## 1. Get sample data\n",
    "\n",
    "To save time, we have prepared a script to load sample data into the Nautilus format for use with this example. \n",
    "First, download and load the data by running the next cell (this should take ~ 1-2 mins):\n",
    "\n",
    "```bash\n",
    "!apt-get update && apt-get install curl -y\n",
    "!curl https://raw.githubusercontent.com/nautechsystems/nautilus_data/main/nautilus_data/hist_data_to_catalog.py | python -\n",
    "```\n",
    "\n",
    "For further details on how to load data into Nautilus, see [Loading External Data](https://nautilustrader.io/docs/latest/concepts/data#loading-data) guide."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from nautilus_trader.backtest.node import BacktestDataConfig\n",
    "from nautilus_trader.backtest.node import BacktestEngineConfig\n",
    "from nautilus_trader.backtest.node import BacktestNode\n",
    "from nautilus_trader.backtest.node import BacktestRunConfig\n",
    "from nautilus_trader.backtest.node import BacktestVenueConfig\n",
    "from nautilus_trader.config import ImportableStrategyConfig\n",
    "from nautilus_trader.config import LoggingConfig\n",
    "from nautilus_trader.model import Quantity\n",
    "from nautilus_trader.model import QuoteTick\n",
    "from nautilus_trader.persistence.catalog import ParquetDataCatalog"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5",
   "metadata": {},
   "source": [
    "## 2. Set up a Parquet data catalog\n",
    "\n",
    "If everything worked correctly, you should be able to see a single EUR/USD instrument in the catalog."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# You can also use a relative path such as `ParquetDataCatalog(\"./catalog\")`,\n",
    "# for example if you're running this notebook after the data setup from the docs.\n",
    "# catalog = ParquetDataCatalog(\"./catalog\")\n",
    "catalog = ParquetDataCatalog.from_env()\n",
    "catalog.instruments()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "## 3. Write a trading strategy\n",
    "\n",
    "NautilusTrader includes many indicators built-in, in this example we will use the MACD indicator to \n",
    "build a simple trading strategy.\n",
    "\n",
    "You can read more about [MACD here](https://www.investopedia.com/terms/m/macd.asp), this \n",
    "indicator merely serves as an example without any expected alpha. There is also a way of\n",
    "registering indicators to receive certain data types, however in this example we manually pass the received\n",
    "`QuoteTick` to the indicator in the `on_quote_tick` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from nautilus_trader.core.message import Event\n",
    "from nautilus_trader.indicators.macd import MovingAverageConvergenceDivergence\n",
    "from nautilus_trader.model import InstrumentId\n",
    "from nautilus_trader.model import Position\n",
    "from nautilus_trader.model.enums import OrderSide\n",
    "from nautilus_trader.model.enums import PositionSide\n",
    "from nautilus_trader.model.enums import PriceType\n",
    "from nautilus_trader.model.events import PositionOpened\n",
    "from nautilus_trader.trading.strategy import Strategy\n",
    "from nautilus_trader.trading.strategy import StrategyConfig\n",
    "\n",
    "\n",
    "class MACDConfig(StrategyConfig):\n",
    "    instrument_id: InstrumentId\n",
    "    fast_period: int = 12\n",
    "    slow_period: int = 26\n",
    "    trade_size: int = 1_000_000\n",
    "    entry_threshold: float = 0.00010\n",
    "\n",
    "\n",
    "class MACDStrategy(Strategy):\n",
    "    def __init__(self, config: MACDConfig):\n",
    "        super().__init__(config=config)\n",
    "        # Our \"trading signal\"\n",
    "        self.macd = MovingAverageConvergenceDivergence(\n",
    "            fast_period=config.fast_period, slow_period=config.slow_period, price_type=PriceType.MID\n",
    "        )\n",
    "\n",
    "        self.trade_size = Quantity.from_int(config.trade_size)\n",
    "\n",
    "        # Convenience\n",
    "        self.position: Position | None = None\n",
    "\n",
    "    def on_start(self):\n",
    "        self.subscribe_quote_ticks(instrument_id=self.config.instrument_id)\n",
    "\n",
    "    def on_stop(self):\n",
    "        self.close_all_positions(self.config.instrument_id)\n",
    "        self.unsubscribe_quote_ticks(instrument_id=self.config.instrument_id)\n",
    "\n",
    "    def on_quote_tick(self, tick: QuoteTick):\n",
    "        # You can register indicators to receive quote tick updates automatically,\n",
    "        # here we manually update the indicator to demonstrate the flexibility available.\n",
    "        self.macd.handle_quote_tick(tick)\n",
    "\n",
    "        if not self.macd.initialized:\n",
    "            return  # Wait for indicator to warm up\n",
    "\n",
    "        # self._log.info(f\"{self.macd.value=}:%5d\")\n",
    "        self.check_for_entry()\n",
    "        self.check_for_exit()\n",
    "\n",
    "    def on_event(self, event: Event):\n",
    "        if isinstance(event, PositionOpened):\n",
    "            self.position = self.cache.position(event.position_id)\n",
    "\n",
    "    def check_for_entry(self):\n",
    "        # If MACD line is above our entry threshold, we should be LONG\n",
    "        if self.macd.value > self.config.entry_threshold:\n",
    "            if self.position and self.position.side == PositionSide.LONG:\n",
    "                return  # Already LONG\n",
    "\n",
    "            order = self.order_factory.market(\n",
    "                instrument_id=self.config.instrument_id,\n",
    "                order_side=OrderSide.BUY,\n",
    "                quantity=self.trade_size,\n",
    "            )\n",
    "            self.submit_order(order)\n",
    "        # If MACD line is below our entry threshold, we should be SHORT\n",
    "        elif self.macd.value < -self.config.entry_threshold:\n",
    "            if self.position and self.position.side == PositionSide.SHORT:\n",
    "                return  # Already SHORT\n",
    "\n",
    "            order = self.order_factory.market(\n",
    "                instrument_id=self.config.instrument_id,\n",
    "                order_side=OrderSide.SELL,\n",
    "                quantity=self.trade_size,\n",
    "            )\n",
    "            self.submit_order(order)\n",
    "\n",
    "    def check_for_exit(self):\n",
    "        # If MACD line is above zero then exit if we are SHORT\n",
    "        if self.macd.value >= 0.0:\n",
    "            if self.position and self.position.side == PositionSide.SHORT:\n",
    "                self.close_position(self.position)\n",
    "        # If MACD line is below zero then exit if we are LONG\n",
    "        else:\n",
    "            if self.position and self.position.side == PositionSide.LONG:\n",
    "                self.close_position(self.position)\n",
    "\n",
    "    def on_dispose(self):\n",
    "        pass  # Do nothing else"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "## Configuring backtests\n",
    "\n",
    "Now that we have a trading strategy and data, we can begin to configure a backtest run. Nautilus uses a `BacktestNode` \n",
    "to orchestrate backtest runs, which requires some setup. This may seem a little complex at first, \n",
    "however this is necessary for the capabilities that Nautilus strives for.\n",
    "\n",
    "To configure a `BacktestNode`, we first need to create an instance of a `BacktestRunConfig`, configuring the \n",
    "following (minimal) aspects of the backtest:\n",
    "\n",
    "- `engine`: The engine for the backtest representing our core system, which will also contain our strategies\n",
    "- `venues`: The simulated venues (exchanges or brokers) available in the backtest\n",
    "- `data`: The input data we would like to perform the backtest on\n",
    "\n",
    "There are many more configurable features which will be described later in the docs, for now this will get us up and running.\n",
    "\n",
    "## 4. Configure venue\n",
    "\n",
    "First, we create a venue configuration. For this example we will create a simulated FX ECN. \n",
    "A venue needs a name which acts as an ID (in this case `SIM`), as well as some basic configuration, e.g. \n",
    "the account type (`CASH` vs `MARGIN`), an optional base currency, and starting balance(s).\n",
    "\n",
    ":::note\n",
    "FX trading is typically done on margin with Non-Deliverable Forward, Swap or CFD type instruments.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "venue = BacktestVenueConfig(\n",
    "    name=\"SIM\",\n",
    "    oms_type=\"NETTING\",\n",
    "    account_type=\"MARGIN\",\n",
    "    base_currency=\"USD\",\n",
    "    starting_balances=[\"1_000_000 USD\"]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11",
   "metadata": {},
   "source": [
    "## 5. Configure data\n",
    "\n",
    "We need to know about the instruments that we would like to load data for, we can use the `ParquetDataCatalog` for this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "instruments = catalog.instruments()\n",
    "instruments"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "Next, we need to configure the data for the backtest. Nautilus is built to be very flexible when it \n",
    "comes to loading data for backtests, however this also means some configuration is required.\n",
    "\n",
    "For each tick type (and instrument), we add a `BacktestDataConfig`. In this instance we are simply \n",
    "adding the `QuoteTick`(s) for our EUR/USD instrument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "from nautilus_trader.model import QuoteTick\n",
    "\n",
    "\n",
    "data = BacktestDataConfig(\n",
    "    catalog_path=str(catalog.path),\n",
    "    data_cls=QuoteTick,\n",
    "    instrument_id=instruments[0].id,\n",
    "    end_time=\"2020-01-10\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "## 6. Configure engine\n",
    "\n",
    "Then, we need a `BacktestEngineConfig` which represents the configuration of our core trading system.\n",
    "Here we need to pass our trading strategies, we can also adjust the log level \n",
    "and configure many other components (however, it's also fine to use the defaults):\n",
    "\n",
    "Strategies are added via the `ImportableStrategyConfig`, which enables importing strategies from arbitrary files or \n",
    "user packages. In this instance, our `MACDStrategy` is defined in the current module, which python refers to as `__main__`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "# NautilusTrader currently exceeds the rate limit for Jupyter notebook logging (stdout output),\n",
    "# this is why the `log_level` is set to \"ERROR\". If you lower this level to see\n",
    "# more logging then the notebook will hang during cell execution. A fix is currently\n",
    "# being investigated which involves either raising the configured rate limits for\n",
    "# Jupyter, or throttling the log flushing from Nautilus.\n",
    "# https://github.com/jupyterlab/jupyterlab/issues/12845\n",
    "# https://github.com/deshaw/jupyterlab-limit-output\n",
    "engine = BacktestEngineConfig(\n",
    "    strategies=[\n",
    "        ImportableStrategyConfig(\n",
    "            strategy_path=\"__main__:MACDStrategy\",\n",
    "            config_path=\"__main__:MACDConfig\",\n",
    "            config={\n",
    "              \"instrument_id\": instruments[0].id,\n",
    "              \"fast_period\": 12,\n",
    "              \"slow_period\": 26,\n",
    "            },\n",
    "        )\n",
    "    ],\n",
    "    logging=LoggingConfig(log_level=\"ERROR\"),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "## 7. Run backtest\n",
    "\n",
    "We can now pass our various config pieces to the `BacktestRunConfig`. This object now contains the \n",
    "full configuration for our backtest."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "config = BacktestRunConfig(\n",
    "    engine=engine,\n",
    "    venues=[venue],\n",
    "    data=[data],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "The `BacktestNode` class will orchestrate the backtest run. The reason for this separation between \n",
    "configuration and execution is the `BacktestNode`, which enables running multiple configurations (different \n",
    "parameters or batches of data). We are now ready to run some backtests."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "from nautilus_trader.backtest.results import BacktestResult\n",
    "\n",
    "\n",
    "node = BacktestNode(configs=[config])\n",
    "\n",
    " # Runs one or many configs synchronously\n",
    "results: list[BacktestResult] = node.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21",
   "metadata": {},
   "source": [
    "## 8. Analyze results"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22",
   "metadata": {},
   "source": [
    "Now that the run is complete, we can also directly query for the `BacktestEngine`(s) used internally by the `BacktestNode`\n",
    "by using the run configs ID. \n",
    "\n",
    "The engine(s) can provide additional reports and information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "from nautilus_trader.backtest.engine import BacktestEngine\n",
    "from nautilus_trader.model import Venue\n",
    "\n",
    "\n",
    "engine: BacktestEngine = node.get_engine(config.id)\n",
    "\n",
    "engine.trader.generate_order_fills_report()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {},
   "outputs": [],
   "source": [
    "engine.trader.generate_positions_report()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "engine.trader.generate_account_report(Venue(\"SIM\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
